#pragma once

#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <iostream>
#include <string>

/*
    const char *pid_file = "/tmp/test.pid";
    ::unlink(pid_file);
    {
        PidFile pid;
        ASSERT_TRUE(pid.lock(pid_file));
        EXPECT_EQ(::access(pid_file, R_OK), 0); //! 文件应该存在
    }

    EXPECT_EQ(::access(pid_file, R_OK), -1);    //! 文件应该打不开，因为已经删除了
    EXPECT_EQ(errno, ENOENT);
*/
class PidFile
{
public:
    PidFile()
    {
    }

    virtual ~PidFile()
    {
        unlock();
    }

    PidFile(const PidFile &) = delete;
    PidFile &operator=(const PidFile &) = delete;

    bool lock(const std::string &pid_filename)
    {
        if (fd_ >= 0) //! 已经 initialize() 过，直接返回
            return true;

        pid_filename_ = pid_filename;

        int fd = open(pid_filename_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC | O_TRUNC, 0644);
        if (fd < 0)
        {
            std::cerr << "Err: create pid file " << pid_filename_ << " fail. errno:" << errno << ", " << strerror(errno) << std::endl;
            return false;
        }

        //! 加锁
        struct flock flock = {
            .l_type = F_WRLCK,
            .l_whence = SEEK_SET,
            .l_start = 0,
            .l_len = 0,
            .l_pid = 0,
        };

        int ret = fcntl(fd, F_SETLK, &flock);
        if (ret < 0)
        {
            if (errno == EAGAIN) //! 已被其它进程锁住了
                return false;

            std::cerr << "Err: lock pid file fail, errno:" << errno << ", " << strerror(errno) << std::endl;
            return false;
        }

        //! 加锁成功，写入 pid
        pid_t pid = getpid();
        std::string pid_str = std::to_string(pid);
        ssize_t wsize = write(fd, pid_str.c_str(), pid_str.size());
        if (wsize < 0)
            std::cerr << "Warn: write pid fail. errno:" << errno << ", " << strerror(errno) << std::endl;

        fd_ = fd;

        return true;
    }

    bool unlock()
    {
        if (fd_ < 0)
            return false;

        close(fd_);
        fd_ = -1;

        int ret = ::unlink(pid_filename_.c_str());
        if (ret < 0)
            std::cerr << "Warn: unlink pid file " << pid_filename_ << " fail. errno:" << errno << ", " << strerror(errno) << std::endl;

        return true;
    }

private:
    std::string pid_filename_;
    int fd_ = -1;
};